#region Copyright note and modification history

/*
-----------------------------------------------------------------------------
Copyright (c) 2006 HSI Inc. All Rights Reserved.

PROPRIETARY NOTICE: This software has been provided pursuant to a
License Agreement that contains restrictions on its use. This software
contains valuable trade secrets and proprietary information of
HSI Inc and is protected by Federal copyright law. It may not
be copied or distributed in any form or medium, disclosed to third parties,
or used in any manner that is not provided for in said License Agreement,
except with the prior written authorization of HSI Inc.

-----------------------------------------------------------------------------
$Log: $
-----------------------------------------------------------------------------
*/

#endregion

using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

namespace SimplePushpinGenerator
{
    /// <summary>
    /// Summary description for PushPinPainter.
    /// </summary>
    public class PushPinPainter
    {
        private int _noImageHeight = 16;
        private int _noImageWidth = 16;
        private int _pinBaseRatio = 10;
        private Color _pinColor1 = Color.WhiteSmoke;
        private Color _pinColor2 = Color.SlateGray;
        private float _PinHeadBackgroundGradientAngle = 90.0f;

        private Color _PinHeadBackgroundGradientColor1 = Color.FromArgb(5, 123, 255);
        private Color _PinHeadBackgroundGradientColor2 = Color.FromArgb(207, 230, 255);

        private Color _PinHeadBorderColor = Color.SlateGray;

        private float _PinHeadBorderWidth = 1f;

        private int _PinHeadBufferPercent = 30;
        private int _pinLength = 4;
        private int _pinPointWidth = 1;
        private Color _reflectionColor = Color.White;
        private int _reflectionHeightCompression = 6;
        private int _reflectionOpacity = 255;
        private int _reflectionShrinkPercent = 5;
        private int _reflectionWidthCompression = 3;
        private int _reflectionXOffset = 3;

        private int _shadowHeightCompressionRation = 4;

        private int _shadowOpacity = 60;
        private int _shadowWidthCompressionAmount = 6;
        private bool _showImage = true;

        private bool _showPin = true;
        private bool _showPinBorder = true;
        private bool _showReflection = true;
        private bool _showShadow = true;

        private bool _imageOutsideReflection = true;



        [DefaultValue(16)]
        public int NoImageWidth
        {
            get { return _noImageWidth; }
            set { _noImageWidth = value; }
        }

        [DefaultValue(16)]
        public int NoImageHeight
        {
            get { return _noImageHeight; }
            set { _noImageHeight = value; }
        }

        [DefaultValue(0)]
        public int ImageXOffset { get; set; }

        [DefaultValue(0)]
        public int ImageYOffset { get; set; }


        [DefaultValue(true)]
        public bool ImageOutsideReflection
        {
            get { return _imageOutsideReflection; }
            set { _imageOutsideReflection = value; }
        }

        [DefaultValue(true)]
        public bool ShowImage
        {
            get { return _showImage; }
            set { _showImage = value; }
        }


        [DefaultValue(5)]
        public int ReflectionShrinkPercent
        {
            get { return _reflectionShrinkPercent; }
            set { _reflectionShrinkPercent = value; }
        }

        [DefaultValue(6)]
        public int ReflectionHeightCompression
        {
            get { return _reflectionHeightCompression; }
            set { _reflectionHeightCompression = value; }
        }

        [DefaultValue(3)]
        public int ReflectionWidthCompression
        {
            get { return _reflectionWidthCompression; }
            set { _reflectionWidthCompression = value; }
        }

        [DefaultValue(3)]
        public int ReflectionXOffset
        {
            get { return _reflectionXOffset; }
            set { _reflectionXOffset = value; }
        }

        [DefaultValue(0)]
        public int ReflectionYOffset { get; set; }

        [DefaultValue(true)]
        public bool ShowReflection
        {
            get { return _showReflection; }
            set { _showReflection = value; }
        }

        [DefaultValue(255)]
        public int ReflectionOpacity
        {
            get { return _reflectionOpacity; }
            set { _reflectionOpacity = value; }
        }

        [DefaultValue(typeof (Color), "White")]
        public Color ReflectionColor
        {
            get { return _reflectionColor; }
            set { _reflectionColor = value; }
        }

        [DefaultValue(typeof (Color), "5,123,255")]
        public Color PinHeadBackgroundGradientColor1
        {
            get { return _PinHeadBackgroundGradientColor1; }
            set { _PinHeadBackgroundGradientColor1 = value; }
        }

        [DefaultValue(typeof (Color), "207,230,255")]
        public Color PinHeadBackgroundGradientColor2
        {
            get { return _PinHeadBackgroundGradientColor2; }
            set { _PinHeadBackgroundGradientColor2 = value; }
        }

        [DefaultValue(90.0f)]
        public float PinHeadBackgroundGradientAngle
        {
            get { return _PinHeadBackgroundGradientAngle; }
            set { _PinHeadBackgroundGradientAngle = value; }
        }

        [DefaultValue(typeof (Color), "SlateGray")]
        public Color PinHeadBorderColor
        {
            get { return _PinHeadBorderColor; }
            set { _PinHeadBorderColor = value; }
        }

        [DefaultValue(1f)]
        public float PinHeadBorderWidth
        {
            get { return _PinHeadBorderWidth; }
            set { _PinHeadBorderWidth = value; }
        }

        [DefaultValue(30)]
        public int PinHeadBufferPercent
        {
            get { return _PinHeadBufferPercent; }
            set { _PinHeadBufferPercent = value; }
        }

        [DefaultValue(2)]
        public int ShadowHeightCompressionRatio
        {
            get { return _shadowHeightCompressionRation; }
            set { _shadowHeightCompressionRation = value; }
        }

        [DefaultValue(6)]
        public int ShadowWidthCompressionAmount
        {
            get { return _shadowWidthCompressionAmount; }
            set { _shadowWidthCompressionAmount = value; }
        }

        [DefaultValue(60)]
        public int ShadowOpacity
        {
            get { return _shadowOpacity; }
            set { _shadowOpacity = value; }
        }

        [DefaultValue(true)]
        public bool ShowShadow
        {
            get { return _showShadow; }
            set { _showShadow = value; }
        }

        [DefaultValue(10)]
        public int PinBaseRatio
        {
            get { return _pinBaseRatio; }
            set { _pinBaseRatio = value; }
        }

        [DefaultValue(1)]
        public int PinPointWidth
        {
            get { return _pinPointWidth; }
            set { _pinPointWidth = value; }
        }

        [DefaultValue(4)]
        public int PinLength
        {
            get { return _pinLength; }
            set { _pinLength = value; }
        }


        [DefaultValue(typeof (Color), "WhiteSmoke")]
        public Color PinColor1
        {
            get { return _pinColor1; }
            set { _pinColor1 = value; }
        }

        [DefaultValue(typeof (Color), "SlateGray")]
        public Color PinColor2
        {
            get { return _pinColor2; }
            set { _pinColor2 = value; }
        }

        [DefaultValue(true)]
        public bool ShowPin
        {
            get { return _showPin; }
            set { _showPin = value; }
        }

        [DefaultValue(true)]
        public bool ShowPinBorder
        {
            get { return _showPinBorder; }
            set { _showPinBorder = value; }
        }

        [DefaultValue(false)]
        public bool AllowOvalPinHeads { get; set; }

        #region Public Methods

        public void SaveToFile(Image image,string filePath,ImageFormat imageFormat)
        {
            using (Image img = CreateImage(image))
            {
                img.Save(filePath,imageFormat);    
            }
        }

        public void SaveToStream(Image image,Stream stream,ImageFormat imageFormat)
        {
            using (Image img = CreateImage(image))
            {
                img.Save(stream, imageFormat);
            }
        }

        public Image CreateImage(Image image)
        {
            var bmp = new Bitmap(1000, 1000);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                Rectangle bounds = Draw(image, g);
                var final = new Bitmap(bounds.Width + 1, bounds.Height + 1);
                using (Graphics fG = Graphics.FromImage(final))
                {
                    fG.DrawImage(bmp, 0, 0);
                    return final;
                }
            }
        }

        public Rectangle Draw(Graphics graphics)
        {
            return Draw(null, graphics);
        }

        public Rectangle Draw(Image image, Graphics graphics)
        {
            Rectangle pinHeadBounds = GetPinHeadBounds(image);
            Rectangle imageBounds = GetImageBounds(image);
            
            var finalBounds = new Rectangle(pinHeadBounds.X, pinHeadBounds.Y, pinHeadBounds.Width,
                                                  pinHeadBounds.Height);
            
            pinHeadBounds.CenterInside(ref imageBounds);

            graphics.SmoothingMode = SmoothingMode.AntiAlias;

            finalBounds = Rectangle.Union(finalBounds,DrawPinHeadShadow(image, pinHeadBounds, graphics));
            
            finalBounds = Rectangle.Union(finalBounds, DrawPin(pinHeadBounds, graphics));
            
            ShadePinHeadBackground(image, pinHeadBounds, graphics);

            if (ImageOutsideReflection)
            {
                DrawReflection(pinHeadBounds, graphics);
                DrawImage(image, imageBounds, graphics);
            }
            else
            {
                DrawImage(image, imageBounds, graphics);
                DrawReflection(pinHeadBounds, graphics);
            }

            DrawPinHeadBorder(image, pinHeadBounds, graphics);

            return finalBounds;
        }

        #endregion

        #region Non-Public Drawing Methods

        private Rectangle GetPinHeadBounds(Image image)
        {
            Rectangle imageRect = GetImageBounds(image);
            imageRect = imageRect.Grow(PinHeadBufferPercent);
            
            //Growing has moved the image off the top and left edges. We'll nudge it back.
            imageRect.X = 0;
            imageRect.Y = 0;
            return imageRect;
        }

        private Rectangle GetImageBounds(Image image)
        {
            Rectangle imageRect;
            if (image != null)
            {
                if (AllowOvalPinHeads)
                    imageRect = new Rectangle(0, 0, image.Width, image.Height);
                else if (image.Height > image.Width)
                    imageRect = new Rectangle(0, 0, image.Height, image.Height);
                else
                    imageRect = new Rectangle(0, 0, image.Width, image.Width);
            }
            else
            {
                imageRect = new Rectangle(0, 0, NoImageWidth, NoImageHeight);
            }
            return imageRect;
        }

        protected Rectangle DrawPinHeadShadow(Image image, Rectangle pinHeadBounds, Graphics g)
        {
            if (ShowShadow)
            {
                Rectangle shadow = pinHeadBounds;
                shadow.Height = shadow.Height/ShadowHeightCompressionRatio;
                shadow.Width = shadow.Width - ShadowWidthCompressionAmount;

                shadow.Y = (pinHeadBounds.Y + pinHeadBounds.Height) - (shadow.Height/2);
                shadow.X = (pinHeadBounds.X + pinHeadBounds.Width) - (shadow.Width) - (ShadowWidthCompressionAmount/2);

                //if (ShowPin)
                //{
                    shadow.Y += PinLength;
                //}

                float transparencyMultiplier = ShadowOpacity/255.0f;

                float[][] ptsArray = {
                                         new float[] {1, 0, 0, 0, 0},
                                         new float[] {0, 1, 0, 0, 0},
                                         new float[] {0, 0, 1, 0, 0},
                                         new[] {0, 0, 0, transparencyMultiplier, 0},
                                         new float[] {0, 0, 0, 0, 1}
                                     };

                var clrMatrix = new ColorMatrix(ptsArray);
                var imgAttributes = new ImageAttributes();
                imgAttributes.SetColorMatrix(clrMatrix,
                                             ColorMatrixFlag.Default,
                                             ColorAdjustType.Bitmap);

                Image pic = ResourceHelper.LoadBitmap(GetType(), "SimplePushpinGenerator.Images.shadow.png");
                g.DrawImage(pic, shadow, 0, 0, pic.Width, pic.Height, GraphicsUnit.Pixel, imgAttributes);
                return new Rectangle(shadow.X, shadow.Y, shadow.Width, shadow.Height);
            }
            return new Rectangle();
        }

        protected virtual void DrawPinHeadBorder(Image image, Rectangle pinHeadBounds, Graphics g)
        {
            using (var pen = new Pen(PinHeadBorderColor, PinHeadBorderWidth))
            {
                g.DrawEllipse(pen, pinHeadBounds);
            }
        }

        protected virtual void ShadePinHeadBackground(Image image, Rectangle pinHeadBounds, Graphics g)
        {
            Color c1 = PinHeadBackgroundGradientColor1;
            Color c2 = PinHeadBackgroundGradientColor2;
            using (var brush = new LinearGradientBrush(pinHeadBounds, c1, c2, PinHeadBackgroundGradientAngle))
            {
                g.FillEllipse(brush, pinHeadBounds);
            }
        }

        private void DrawImage(Image image, Rectangle imageBounds, Graphics g)
        {
            if (ShowImage)
            {
                if (image != null)
                {
                    var imageCanvas = new Rectangle(0, 0, image.Width, image.Height);
                    imageBounds.CenterInside(ref imageCanvas);

                    imageCanvas.X += ImageXOffset;
                    imageCanvas.Y += ImageYOffset;

                    g.DrawImage(image, imageCanvas);
                }
            }
        }

        private void DrawReflection(Rectangle pinHeadBounds, Graphics g)
        {

            if (ShowReflection)
            {
                Rectangle reflectionBound = pinHeadBounds.Shrink(ReflectionShrinkPercent);
                reflectionBound.Height -= ReflectionHeightCompression;
                reflectionBound.Width -= ReflectionWidthCompression;
                reflectionBound.X += ReflectionXOffset;
                reflectionBound.Y += ReflectionYOffset;

                using (
                    var brush = new LinearGradientBrush(reflectionBound,
                                                        Color.FromArgb(ReflectionOpacity, ReflectionColor),
                                                        Color.Transparent, 90.0f))
                {
                    g.FillEllipse(brush, reflectionBound);
                }
            }
        }

        private Rectangle DrawPin(Rectangle pinHeadBounds, Graphics g)
        {
           
                Rectangle circle = pinHeadBounds;
                var path = new GraphicsPath();

                int midX = circle.Width/2 + circle.X;

                int pinBaseWidth = circle.Width/_pinBaseRatio;
                int pinX = midX - pinBaseWidth/2;
                int pinY = circle.Height + circle.Y - 3;

                path.AddLine(pinX, pinY, pinX + pinBaseWidth, pinY);
                path.AddLine(midX + (_pinPointWidth/2), pinY + _pinLength + 3, midX - (_pinPointWidth/2),
                             pinY + _pinLength + 3);

                path.CloseFigure();

                if (ShowPin)
                {
                    if (ShowPinBorder)
                    {
                        using (var pen = new Pen(PinHeadBorderColor))
                        {
                            g.DrawPath(pen, path);
                        }
                    }

                    using (var brush = new LinearGradientBrush(path.GetBounds(), _pinColor1, _pinColor2, 180))
                    {
                        g.FillPath(brush, path);
                    }
                }

            return Rectangle.Ceiling(path.GetBounds());
            
        }

        #endregion

  }
}